Поиск…


Создание пользовательских представлений

Если вам требуется полностью настроенное представление, вам нужно подклассировать View (суперкласс всех Android-представлений) и предоставить свой собственный размер ( onMeasure(...) ) и onDraw(...) рисования ( onDraw(...) ):

  1. Создайте свой собственный скелет вида: это в основном то же самое для каждого пользовательского представления. Здесь мы создаем скелет для пользовательского представления, которое может нарисовать смайлик под названием SmileyView :

    public class SmileyView extends View {
        private Paint mCirclePaint;
        private Paint mEyeAndMouthPaint;
    
        private float mCenterX;
        private float mCenterY;
        private float mRadius;
        private RectF mArcBounds = new RectF();
    
        public SmileyView(Context context) {
            this(context, null, 0);
        }
    
        public SmileyView(Context context, AttributeSet attrs) {
            this(context, attrs, 0);
        }
    
        public SmileyView(Context context, AttributeSet attrs, int defStyleAttr) {
            super(context, attrs, defStyleAttr);
            initPaints();
        }
    
        private void initPaints() {/* ... */}
    
        @Override
        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {/* ... */}
    
        @Override
        protected void onDraw(Canvas canvas) {/* ... */}
    }
    
  2. Инициализация ваших красок: объекты Paint - это кисти вашего виртуального холста, определяющие, как визуализируются ваши геометрические объекты (например, цвет, стиль заливки и штрихов и т. Д.). Здесь мы создаем две Paint s, одну желтую заполненную краску для круга и одну черную краску для глаз и рта:

    private void initPaints() {
        mCirclePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mCirclePaint.setStyle(Paint.Style.FILL);
        mCirclePaint.setColor(Color.YELLOW);
        mEyeAndMouthPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mEyeAndMouthPaint.setStyle(Paint.Style.STROKE);
        mEyeAndMouthPaint.setStrokeWidth(16 * getResources().getDisplayMetrics().density);
        mEyeAndMouthPaint.setStrokeCap(Paint.Cap.ROUND);
        mEyeAndMouthPaint.setColor(Color.BLACK);
    }
    
  3. Внедрите свой собственный onMeasure(...) : это необходимо, чтобы родительские макеты (например, FrameLayout ) могли правильно выровнять пользовательский вид. Она предоставляет набор measureSpecs , которые можно использовать , чтобы определить высоту и ширину вашего вида. Здесь мы создаем квадрат, убедившись, что высота и ширина одинаковы:

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int w = MeasureSpec.getSize(widthMeasureSpec);
        int h = MeasureSpec.getSize(heightMeasureSpec);
    
        int size = Math.min(w, h);
        setMeasuredDimension(size, size);
    }
    

    Обратите внимание, что onMeasure(...) должен содержать хотя бы один вызов setMeasuredDimension(..) иначе ваше пользовательское представление будет сбой с помощью IllegalStateException .

  4. Внедрите собственный onSizeChanged(...) : это позволяет вам onSizeChanged(...) текущую высоту и ширину вашего пользовательского представления для правильной настройки вашего кода рендеринга. Здесь мы просто вычислим наш центр и наш радиус:

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        mCenterX = w / 2f;
        mCenterY = h / 2f;
        mRadius = Math.min(w, h) / 2f;
    }
    
  5. Внедрите собственный onDraw(...) : здесь вы реализуете фактический рендеринг своего представления. Он предоставляет объект Canvas который вы можете рисовать (см. Официальную документацию Canvas для всех доступных доступных методов рисования).

    @Override
    protected void onDraw(Canvas canvas) {
        // draw face
        canvas.drawCircle(mCenterX, mCenterY, mRadius, mCirclePaint);
        // draw eyes
        float eyeRadius = mRadius / 5f;
        float eyeOffsetX = mRadius / 3f;
        float eyeOffsetY = mRadius / 3f;
        canvas.drawCircle(mCenterX - eyeOffsetX, mCenterY - eyeOffsetY, eyeRadius, mEyeAndMouthPaint);
        canvas.drawCircle(mCenterX + eyeOffsetX, mCenterY - eyeOffsetY, eyeRadius, mEyeAndMouthPaint);
        // draw mouth
        float mouthInset = mRadius /3f;
        mArcBounds.set(mouthInset, mouthInset, mRadius * 2 - mouthInset, mRadius * 2 - mouthInset);
        canvas.drawArc(mArcBounds, 45f, 90f, false, mEyeAndMouthPaint);
    }
    
  6. Добавьте свой собственный вид в макет: теперь пользовательский вид может быть включен в любые файлы макетов, которые у вас есть. Здесь мы просто обертываем его внутри FrameLayout :

    <FrameLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
    
        <com.example.app.SmileyView
            android:layout_width="match_parent"
            android:layout_height="match_parent" />
    </FrameLayout>
    

Обратите внимание: рекомендуется, чтобы проект был создан после завершения кода представления. Без его создания вы не сможете увидеть представление на экране предварительного просмотра в Android Studio.

После того, как вы собрали все вместе, вы должны приветствовать следующий экран после запуска операции, содержащей вышеуказанный макет:

SmileyView используется внутри деятельности

Добавление атрибутов в представления

Пользовательские представления также могут принимать пользовательские атрибуты, которые могут использоваться в файлах ресурсов макета Android. Чтобы добавить атрибуты в пользовательский вид, вам необходимо сделать следующее:

  1. Определите имя и тип ваших атрибутов: это делается внутри res/values/attrs.xml (при необходимости создайте его). Следующий файл определяет атрибут цвета для цвета лица смайлика и атрибут enum для выражения смайлика:

    <resources>
        <declare-styleable name="SmileyView">
            <attr name="smileyColor" format="color" />
            <attr name="smileyExpression" format="enum">
                <enum name="happy" value="0"/>
                <enum name="sad" value="1"/>
            </attr>
        </declare-styleable>
        <!-- attributes for other views -->
    </resources>
    
  2. Используйте свои атрибуты внутри своего макета: это можно сделать в любых файлах макета, которые используют ваш пользовательский вид. Следующий файл макета создает экран со счастливым желтым смайликом:

    <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:layout_height="match_parent"
        android:layout_width="match_parent">
        
        <com.example.app.SmileyView
            android:layout_height="56dp"
            android:layout_width="56dp"
            app:smileyColor="#ffff00"
            app:smileyExpression="happy" />
    </FrameLayout>
    

    Совет. Пользовательские атрибуты не работают с tools: префикс в Android Studio 2.1 и старше (и, возможно, в будущих версиях). В этом примере замена app:smileyColor на tools:smileyColor приведет к tools:smileyColor что smileyColor не будет установлен во время выполнения или во время разработки.

  3. Прочтите свои атрибуты: это делается внутри вашего пользовательского исходного кода. Следующий фрагмент SmileyView демонстрирует, как можно извлечь атрибуты:

    public class SmileyView extends View {
        // ...
    
        public SmileyView(Context context) {
            this(context, null);
        }
    
        public SmileyView(Context context, AttributeSet attrs) {
            this(context, attrs, 0);
        }
    
        public SmileyView(Context context, AttributeSet attrs, int defStyleAttr) {
            super(context, attrs, defStyleAttr);
            
            TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.SmileyView, defStyleAttr, 0);
            mFaceColor = a.getColor(R.styleable.SmileyView_smileyColor, Color.TRANSPARENT);
            mFaceExpression = a.getInteger(R.styleable.SmileyView_smileyExpression, Expression.HAPPY);
            // Important: always recycle the TypedArray
            a.recycle();
    
            // initPaints(); ...
        }
    }
    
  4. (Необязательно) Добавить стиль по умолчанию: это делается путем добавления стиля со значениями по умолчанию и загрузки его в пользовательское представление. Следующий стиль смайлика по умолчанию представляет собой счастливый желтый цвет:

    <!-- styles.xml -->
    <style name="DefaultSmileyStyle">
        <item name="smileyColor">#ffff00</item>
        <item name="smileyExpression">happy</item>
    </style>
    

    Что применяется в нашем SmileyView , добавив его в качестве последнего параметра вызова для obtainStyledAttributes (см. Код на шаге 3):

    TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.SmileyView, defStyleAttr, R.style.DefaultSmileyViewStyle);
    

    Обратите внимание, что любые значения атрибутов, установленные в файле раздутого макета (см. Код на шаге 2), переопределяют соответствующие значения стиля по умолчанию.

  5. (Необязательно). Предоставляйте стили внутри тем: это делается путем добавления нового атрибута ссылки на стиль, который можно использовать внутри ваших тем и предоставления стиля для этого атрибута. Здесь мы просто назовем наш ссылочный атрибут smileyStyle :

    <!-- attrs.xml -->
    <attr name="smileyStyle" format="reference" />
    

    Затем мы предоставляем стиль для нашей темы приложения (здесь мы просто используем стиль по умолчанию с шага 4):

    <!-- themes.xml -->
    <style name="AppTheme" parent="AppBaseTheme">
        <item name="smileyStyle">@style/DefaultSmileyStyle</item>
    </style>
    

Создание сложного вида

ViewGroup вид представляет собой настраиваемую ViewGroup которая рассматривается как единое представление по окружающему программному коду. Такая ViewGroup может быть действительно полезна в DDD- подобном дизайне, поскольку она может соответствовать совокупности в этом примере Contact. Его можно повторно использовать везде, где отображается контакт.

Это означает, что окружающий код контроллера, Activity, Fragment или Adapter может просто передать объект данных в представление, не разделяя его на несколько различных виджетах пользовательского интерфейса.

Это облегчает повторное использование кода и обеспечивает лучшую конструкцию в соответствии с SOLID priciples .

Макет XML

Обычно вы начинаете. У вас есть существующий бит XML, который вы повторно используете, возможно, как <include/> . Извлеките его в отдельный XML-файл и оберните корневой тег в элемент <merge> :

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

        <ImageView
            android:id="@+id/photo"
            android:layout_width="48dp"
            android:layout_height="48dp"
            android:layout_alignParentRight="true" />

        <TextView
            android:id="@+id/name"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_toLeftOf="@id/photo" />

        <TextView
            android:id="@+id/phone_number"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_below="@id/name"
            android:layout_toLeftOf="@id/photo" />
</merge>

Этот XML-файл отлично работает в редакторе макетов в Android Studio. Вы можете рассматривать его как любой другой макет.

Составная группа ViewGroup

После того, как у вас есть файл XML, создайте настраиваемую группу представлений.

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.RelativeLayout;
import android.widget.ImageView;
import android.widget.TextView;

import myapp.R;

/**
 * A compound view to show contacts.
 *
 * This class can be put into an XML layout or instantiated programmatically, it
 * will work correctly either way.
 */
public class ContactView extends RelativeLayout {

    // This class extends RelativeLayout because that comes with an automatic
    // (MATCH_PARENT, MATCH_PARENT) layout for its child item. You can extend
    // the raw android.view.ViewGroup class if you want more control. See the
    // note in the layout XML why you wouldn't want to extend a complex view
    // such as RelativeLayout.

    // 1. Implement superclass constructors.
    public ContactView(Context context) {
        super(context);
        init(context, null);
    }

    // two extra constructors left out to keep the example shorter

    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
    public ContactView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
        init(context, attrs);
    }

    // 2. Initialize the view by inflating an XML using `this` as parent
    private TextView mName;
    private TextView mPhoneNumber;
    private ImageView mPhoto;

    private void init(Context context, AttributeSet attrs) {
        LayoutInflater.from(context).inflate(R.layout.contact_view, this, true);
        mName = (TextView) findViewById(R.id.name);
        mPhoneNumber = (TextView) findViewById(R.id.phone_number);
        mPhoto = (ImageView) findViewById(R.id.photo);
    }

    // 3. Define a setter that's expressed in your domain model. This is what the example is
    //    all about. All controller code can just invoke this setter instead of fiddling with
    //    lots of strings, visibility options, colors, animations, etc. If you don't use a
    //    custom view, this code will usually end up in a static helper method (bad) or copies 
    //    of this code will be copy-pasted all over the place (worse).
    public void setContact(Contact contact) {
        mName.setText(contact.getName());
        mPhoneNumber.setText(contact.getPhoneNumber());
        if (contact.hasPhoto()) {
            mPhoto.setVisibility(View.VISIBLE);
            mPhoto.setImageBitmap(contact.getPhoto());
        } else {
            mPhoto.setVisibility(View.GONE);
        }
    }
}

Метод init(Context, AttributeSet) - это то, где вы читали бы любые пользовательские атрибуты XML, как описано в разделе «Добавление атрибутов в представления» .

Используя эти штуки, вы можете использовать его в своем приложении.

Использование в XML

Вот пример fragment_contact_info.xml который иллюстрирует, как вы поместили бы один ContactView поверх списка сообщений:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <!-- The compound view becomes like any other view XML element -->
    <myapp.ContactView
        android:id="@+id/contact"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"/>

    <android.support.v7.widget.RecyclerView
        android:id="@+id/message_list"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1"/>

</LinearLayout>

Использование в кодексе

Вот пример RecyclerView.Adapter который показывает список контактов. Этот пример иллюстрирует, насколько сильно становится код контроллера, когда он полностью свободен от манипуляций View.

package myapp;

import android.content.Context;
import android.support.v7.widget.RecyclerView;
import android.view.ViewGroup;

public class ContactsAdapter extends RecyclerView.Adapter<ContactsViewHolder> {

    private final Context context;

    public ContactsAdapter(final Context context) {
        this.context = context;
    }

    @Override
    public ContactsViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        ContactView v = new ContactView(context); // <--- this
        return new ContactsViewHolder(v);
    }

    @Override
    public void onBindViewHolder(ContactsViewHolder holder, int position) {
        Contact contact = this.getItem(position);
        holder.setContact(contact);  // <--- this
    }

    static class ContactsViewHolder extends RecyclerView.ViewHolder {

        public ContactsViewHolder(ContactView itemView) {
            super(itemView);
        }

        public void setContact(Contact contact) {
            ((ContactView) itemView).setContact(contact); // <--- this
        }
    }
}

Советы по настройке CustomView

Не выделяйте новые объекты в onDraw

@Override
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    Paint paint = new Paint(); //Do not allocate here
}

Вместо рисования чертежей в холсте ...

drawable.setBounds(boundsRect);

drawable.draw(canvas);

Используйте Bitmap для более быстрого рисования:

canvas.drawBitmap(bitmap, srcRect, boundsRect, paint);

Не перерисовывайте весь вид, чтобы обновить только небольшую часть. Вместо этого перерисуйте определенную часть представления.

invalidate(boundToBeRefreshed);

Если ваше представление делает некоторую непрерывную анимацию, например, часовую onStop() показывающую каждую секунду, по крайней мере останавливает анимацию на onStop() активности и запускает ее на onStart() активности.

Не делайте никаких вычислений внутри метода onDraw вида, вы должны закончить рисование перед вызовом invalidate() . Используя эту технику, вы можете избежать падения кадров в вашем представлении.

Повороты

Основные операции представления - это перевод, поворот и т. Д. Практически каждый разработчик столкнулся с этой проблемой, когда они используют растровые или градиенты в своем пользовательском представлении. Если в представлении будет показано повернутое представление, и растровое изображение должно быть повернуто в этом пользовательском представлении, многие из нас подумают, что это будет дорого. Многие считают, что поворот растрового изображения очень дорог, потому что для этого вам нужно перевести матрицу пикселей растрового изображения. Но правда в том, что это не так сложно! Вместо поворота растрового изображения просто поверните сам холст!

// Save the canvas state
int save = canvas.save();
// Rotate the canvas by providing the  center point as pivot and angle
canvas.rotate(pivotX, pivotY, angle);
// Draw whatever you want
// Basically whatever you draw here will be drawn as per the angle you rotated the canvas
canvas.drawBitmap(...);
// Now restore your your canvas to its original state
canvas.restore(save);
// Unless canvas is restored to its original state, further draw will also be rotated.

Соединение для SVG / VectorDrawable как drawableRight

Главным мотивом для разработки этого сложного представления является то, что ниже 5.0 устройств не поддерживает svg в drawable внутри TextView / EditText. Еще одно преимущество - мы можем установить height и width drawableRight внутри EditText . Я отделил его от моего проекта и создал в отдельном модуле.

Имя модуля: custom_edit_drawable (сокращенное имя для prefix-c_d_e)

Префикс «c_d_e_» для использования, чтобы ресурсы модуля приложения не должны переопределять их по ошибке. Пример: префикс «abc» используется Google в библиотеке поддержки.

build.gradle

dependencies {
   compile 'com.android.support:appcompat-v7:25.3.1'
}

используйте AppCompat> = 23

Файл макета: c_e_d_compound_view.xml

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content">

    <EditText
        android:id="@+id/edt_search"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:inputType="text"
        android:maxLines="1"
        android:paddingEnd="40dp"
        android:paddingLeft="5dp"
        android:paddingRight="40dp"
        android:paddingStart="5dp" />

    <!--make sure you are not using ImageView instead of this-->
    <android.support.v7.widget.AppCompatImageView
        android:id="@+id/drawbleRight_search"
        android:layout_width="30dp"
        android:layout_height="30dp"
        android:layout_gravity="right|center_vertical"
        android:layout_marginLeft="8dp"
        android:layout_marginRight="8dp" />
</FrameLayout>

Пользовательские атрибуты: attrs.xml

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="EditTextWithDrawable">
        <attr name="c_e_d_drawableRightSVG" format="reference" />
        <attr name="c_e_d_hint" format="string" />
        <attr name="c_e_d_textSize" format="dimension" />
        <attr name="c_e_d_textColor" format="color" />
    </declare-styleable>
</resources>

Код: EditTextWithDrawable.java

public class EditTextWithDrawable extends FrameLayout {
    public AppCompatImageView mDrawableRight;
    public EditText mEditText;

    public EditTextWithDrawable(Context context) {
        super(context);
        init(null);
    }

    public EditTextWithDrawable(Context context, AttributeSet attrs) {
        super(context, attrs);
        init(attrs);
    }

    public EditTextWithDrawable(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init(attrs);
    }

    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
    public EditTextWithDrawable(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
        init(attrs);
    }

    private void init(AttributeSet attrs) {
        if (attrs != null && !isInEditMode()) {
            LayoutInflater inflater = (LayoutInflater) getContext()
                    .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
            inflater.inflate(R.layout.c_e_d_compound_view, this, true);
            mDrawableRight = (AppCompatImageView) ((FrameLayout) getChildAt(0)).getChildAt(1);
            mEditText = (EditText) ((FrameLayout) getChildAt(0)).getChildAt(0);

            TypedArray attributeArray = getContext().obtainStyledAttributes(
                    attrs,
                    R.styleable.EditTextWithDrawable);

            int drawableRes =
                    attributeArray.getResourceId(
                            R.styleable.EditTextWithDrawable_c_e_d_drawableRightSVG, -1);
            if (drawableRes != -1) {
                mDrawableRight.setImageResource(drawableRes);
            }

            mEditText.setHint(attributeArray.getString(
                    R.styleable.EditTextWithDrawable_c_e_d_hint));
            mEditText.setTextColor(attributeArray.getColor(
                    R.styleable.EditTextWithDrawable_c_e_d_textColor, Color.BLACK));
            int textSize = attributeArray.getDimensionPixelSize(R.styleable.EditTextWithDrawable_c_e_d_textSize, 15);
            mEditText.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize);
            android.view.ViewGroup.LayoutParams layoutParams = mDrawableRight.getLayoutParams();
            layoutParams.width = (textSize * 3) / 2;
            layoutParams.height = (textSize * 3) / 2;
            mDrawableRight.setLayoutParams(layoutParams);

            attributeArray.recycle();
        }
    }
}

Пример: как использовать вид сверху

Макет: activity_main.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="vertical">

    <com.customeditdrawable.AppEditTextWithDrawable
        android:id="@+id/edt_search_emp"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        app:c_e_d_drawableRightSVG="@drawable/ic_svg_search"
        app:c_e_d_hint="@string/hint_search_here"
        app:c_e_d_textColor="@color/text_color_dark_on_light_bg"
        app:c_e_d_textSize="@dimen/text_size_small" />
</LinearLayout> 

Деятельность: MainActivity.java

public class MainActivity extends AppCompatActivity {
    EditTextWithDrawable mEditTextWithDrawable;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mEditTextWithDrawable= (EditTextWithDrawable) findViewById(R.id.edt_search_emp);
    }
}

Реагирование на события Touch

Многие пользовательские представления должны принимать пользовательское взаимодействие в виде сенсорных событий. Вы можете получить доступ к событиям касания, переопределив onTouchEvent . Существует ряд действий, которые вы можете отфильтровать. Основные из них:

  • ACTION_DOWN : Это срабатывает один раз, когда ваш палец сначала касается вида.
  • ACTION_MOVE : Это называется каждый раз, когда ваш палец немного перемещается по представлению. Его называют много раз.
  • ACTION_UP : Это последнее действие, которое нужно вызывать, когда вы поднимаете палец с экрана.

Вы можете добавить следующий вид к вашему представлению, а затем наблюдать за выходом журнала, когда вы касаетесь и перемещаете свой палец вокруг своего вида.

@Override
public boolean onTouchEvent(MotionEvent event) {

    int x = (int) event.getX();
    int y = (int) event.getY();
    int action = event.getAction();

    switch (action) {
        case MotionEvent.ACTION_DOWN:
            Log.i("CustomView", "onTouchEvent: ACTION_DOWN: x = " + x + ", y = " + y);
            break;

        case MotionEvent.ACTION_MOVE:
            Log.i("CustomView", "onTouchEvent: ACTION_MOVE: x = " + x + ", y = " + y);
            break;

        case MotionEvent.ACTION_UP:
            Log.i("CustomView", "onTouchEvent: ACTION_UP: x = " + x + ", y = " + y);
            break;
    }
    return true;
}

Дальнейшее чтение:



Modified text is an extract of the original Stack Overflow Documentation
Лицензировано согласно CC BY-SA 3.0
Не связан с Stack Overflow